# 学习valueOf和toString,理解隐式转化规则
js在比较运算过程中经常会发生隐式转换,常常会给人意料外的结果,而隐式转换在面试过程中又经常会被考到,所以打算好好整理一下隐式转换这个知识点,本文算是个人对隐式转换的学习梳理。
# 1.toString和valueOf
toString:toString()函数的作用是返回object的字符串表示
- Array 返回数组元素的字符串,默认以逗号链接。
- Boolean 布尔值的字符串值
- Date 日期UTC标准格式
- Function 函数的字符串值
- Number 数字值的字符串值
- Object [Object Object]
- String 字符串值
- Reg 正则的字符串值
如下代码演示(以下演示情况是toString方法没有被重写)
let num = 1
let str = 'a'
let bool = true
let obj = {}
let date = new Date()
let reg = /\d/
let arr = [1, 2, 3]
let fun = function () {
}
console.log(num.toString()) // '1'
console.log(str.toString()) // 'a'
console.log(bool.toString()) // 'true'
console.log(obj.toString()) // '[object Object]'
console.log(date.toString()) // 'Thu Mar 28 2019 17:07:40 GMT+0800 (中国标准时间)'
console.log(reg.toString()) // '/\d/'
console.log(arr.toString()) // '1,2,3'
console.log(fun.toString()) // 'function(){}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
valueOf:valueOf()函数将对象转换为原始值
- Array 返回数组对象本身
- Boolean 布尔值
- Date 返回时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC
- Function 函数本身
- Number 数字值
- Object 对象本身,这是默认情况。
- String 字符串值
- Reg 正则本身
如下代码演示(以下演示情况是valueOf方法没有被重写)
console.log(num.valueOf()) // 1
console.log(str.valueOf()) // 'a'
console.log(bool.valueOf()) // true
console.log(obj.valueOf()) // {}
console.log(date.valueOf()) // 1553766610534
console.log(reg.valueOf()) // /\d/
console.log(arr.valueOf()) // [1, 2, 3]
console.log(fun.valueOf()) // fun()
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
以上铺垫了这么多,是因为在隐式转换过程中经常会有用到调用对象的toString和valueOf方法
# 2. 隐式转换规则
以下为隐式转换时的规则:
- 转化成字符串:使用字符串连接符 +
- 转化成数字: 2.1 ++/-- (自加/自减) 2.2 + - * / % (算术运算)2.3 > < >= <= == != === !== (关系运算符)
- 转成布尔值:使用!非运算符
# 2.1 字符串连接符和算法运算符混淆
先看看以下代码:
let a = 1
console.log(a + '1') // '11'
console.log(a + null) // 1
console.log(a + undefined) // NaN (Number(undefined) = NaN)
console.log(a + true) // 2
console.log(a + {}) // '1[object Object]'
console.log(a + [1, 2, 3]) // '11,2,3'
console.log(a + new Date()) // '1Fri Mar 29 2019 10:12:41 GMT+0800 (中国标准时间)'
console.log(a + /\d/) // '1/\d/'
console.log(a + function(){}) // '1function(){}'
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
从打印的结果可以知道
- 当 + 号为字符串连接符时,则调用对象的toString方法转化为字符串然后相加
- 当 + 号为算术运算符时,则调用Number()方法转化然后相加
- 在这里我们需要注意的是null、布尔值和undefined这三类对象使用 + 进行操作,当有一边确定为数字的时候,这三类值会尝试用Number()进行转化,如果有一边类型确定为字符串的时候,直接就是进行字符串相加。
let a = '1'
console.log(a + null) // '1null'
console.log(a + undefined) // '1undefined'
console.log(a + true) // '1true'
1
2
3
4
2
3
4
2.2 关系运算符会把其他数据类型转换成number之后再比较关系
先看看以下代码:
console.log('2' > 10) // false
console.log('2' > '10') // true
console.log('a' > 'b') // false
console.log('ab' > 'aa') // true
1
2
3
4
2
3
4
从打印的结果可以知道
- 当关系比较有一边为数字的时候,会把其他数据类型调用Number()转化为数字后进行运算
- 当关系比较两边都为字符串的时候,会同时把字符串转化为数字进行比较,但是不是用Number()进行转化,而是按照字符串的unicode编码进行转化(string.charCodeAt,默认为字符的第一位)
console.log('a' > 'b') // false
// 'a'.charCodeAt() > 'b'.charCodeAt()
console.log('ab' > 'aa') // true
// 第一位都是a相等,所以比较第二位的 b.charCodeAt() > a.charCodeAt()
1
2
3
4
2
3
4
# 2.3 复杂数据类型在隐式转换时会先转成String,然后再转成Number运算
复杂类型数据指的是对象或数组这类数据进行隐式转换时,会先调用valueOf后调用toString方法转化成数据,再调用Number()转化成数字进行运算。
如果这个对象的valueOf方法和toString方法被重写过,则会根据valueOf返回的数据类型判断是否执行toString。
接下来代码示范:
let a = {
valueOf: function () {
console.log('执行valueOf')
return 'a'
},
toString: function () {
console.log('执行toString')
return 'a'
}
}
console.log(a == 'a')
// 执行valueOf
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
接下来尝试把valueOf返回值改成数字:
let a = {
valueOf: function () {
console.log('执行valueOf')
return 1
},
toString: function () {
console.log('执行toString')
return 'a'
}
}
console.log(a == 'a')
// 执行valueOf
// false
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
尝试把valueOf返回值改成对象
let a = {
valueOf: function () {
console.log('执行valueOf')
return {}
},
toString: function () {
console.log('执行toString')
return 'a'
}
}
console.log(a == 'a')
// 执行valueOf
// 执行toString
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
通过上面的例子我们可以得出结论:
- valueOf返回的数据类型决定是否调用toString,如果返回的类型是数字或者字符串(其实用基础数据类型更准确点),toString方法就不执行了。
- 转化成字符串后再调用Number()转化成数字进行比较 这里还有个问题就是如果toString方法返回不是基础类型,进行比较的时候则会报错。
# 2.4 逻辑非隐式转换与关系运算符隐式转换混淆
当使用!逻辑非运算符进行转化的时候,会尝试把数据转化成布尔值
以下情况使用Boolean()转化将会得到false
0、-0、undefined、null、NaN、false、''(空字符串)、document.all
console.log([] == 0) // true
console.log(![] == 0) // true
// [] == 0 --> [].valueOf().toString()得到空字符串,Number('') == 0 成立
// ![] == 0 --> Boolean([])得到true再取反,最后转化成数字0,Number(!true) == 0 成立
console.log([] == ![]) // true
console.log([] == []) // false
// [] == ![] --> [].valueOf().toString()得到空字符串,Number('')取得0,Boolean([])得到true再取反,转化成数字0,最后Number('') == Number(!true) 成立
// [] == [] --> 两个数组比较是因为两个数据的引用指向不一致,所以 [] == [] 不成立
console.log({} == !{}) // false
console.log({} == {}) // false
// {} == !{} --> {}.valueOf().toString()得到'[object Object]',Boolean({})得到true再取反,所以 '[object Object]' == false 不成立
// {} == {} --> 两个对象比较是因为两个数据的引用指向不一致,所以 {} == {} 不成立
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
最后总结一下,在复杂数据类型隐式转化过程中会调用valueOf和toString方法,所以如果这两个方法被改写了往往会得到一些意料外的结果。